package com.answer.service.scoring;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.answer.common.ErrorCode;
import com.answer.entity.App;
import com.answer.entity.Question;
import com.answer.entity.UserAnswer;
import com.answer.model.dto.question.QuestionAnswerDTO;
import com.answer.model.dto.question.QuestionContentDTO;
import com.answer.model.vo.QuestionVO;
import com.answer.service.QuestionService;
import com.answer.service.manager.AiManager;
import com.answer.utils.ThrowUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * AI 测评类应用评分策略
 */
@ScoringStrategyConfig(appType = 1, scoringStrategy = 1)
public class AiTestScoringStrategy implements ScoringStrategy {

    @Resource
    private QuestionService questionService;

    @Resource
    private AiManager aiManager;

    @Resource
    private RedissonClient redissonClient;

    public static final String AI_ANSWER_LOCK = "AI_ANSWER_LOCK";

    /**
     * AI 评分结果本地缓存，初始化容量以及过期时间
     */
    private final Cache<String, String> answerCacheMap =
            Caffeine.newBuilder().initialCapacity(1024)
                    .expireAfterAccess(5L, TimeUnit.MINUTES)
                    .build();

    /**
     * AI 评分系统消息
     */
    private static final String AI_TEST_SCORING_SYSTEM_MESSAGE = """
            你是一位严谨的判题专家，我会给你如下信息：
            ```
            应用名称，
            【【【应用描述】】】，
            题目和用户回答的列表：格式为 [{"title": "题目","answer": "用户回答"}]
            ```

            请你根据上述信息，按照以下步骤来对用户进行评价：
            1. 要求：需要给出一个明确的评价结果，包括评价名称（尽量简短）和评价描述（尽量详细，大于 200 字）
            2. 严格按照下面的 json 格式输出评价名称和评价描述
            ```
            {"resultName": "评价名称", "resultDesc": "评价描述"}
            ```
            3. 返回格式必须为 JSON 对象""";

    /**
     * 根据用户的选择列表和应用实例进行评分
     *
     * @param choices 用户的选择列表，用于评分
     * @param app     应用实例，评分操作需要的应用信息
     * @return 返回评分结果的用户答案对象
     * @throws Exception 可能抛出异常，表示评分过程中出现错误
     */
    @Override
    public UserAnswer doScore(List<String> choices, App app) throws Exception {
        ThrowUtils.throwIf(app == null, ErrorCode.PARAMS_ERROR);
        // 1.查询缓存
        Long appId = app.getId();
        // 创建本地缓存key
        String jsonStr = JSONUtil.toJsonStr(choices);
        String cacheKey = buildCacheKey(appId, jsonStr);
        String answerJson = answerCacheMap.getIfPresent(cacheKey);
        // 如果缓存中有结果，直接返回
        if (StrUtil.isNotBlank(answerJson)) {
            answerJson.replaceAll("\\s", "");
            UserAnswer userAnswer = JSONUtil.toBean(answerJson, UserAnswer.class);
            userAnswer.setAppId(appId);
            userAnswer.setAppType(app.getAppType());
            userAnswer.setScoringStrategy(app.getScoringStrategy());
            userAnswer.setChoices(jsonStr);
            return userAnswer;
        }
        // 如果无缓存，加分布式锁
        RLock lock = redissonClient.getLock(AI_ANSWER_LOCK + cacheKey);
        try {
            // 竞争锁
            boolean res = lock.tryLock(3, 15, TimeUnit.SECONDS);
            // 未抢到锁强行返回
            if (!res) {
                return null;
            }// 抢到锁执行后续业务
            // 并根据 appId 查询到题目和题目结果信息（app表没有题目id）
            Question question = questionService.getOne(
                    Wrappers.lambdaQuery(Question.class).eq(Question::getAppId, appId)
            );
            QuestionVO questionVO = QuestionVO.objToVo(question);
            List<QuestionContentDTO> questionContentDTOList = questionVO.getQuestionContent();
            // 2.调用 AI 评分获取结果
            String userMessage = getAiTestScoringUserMessage(app, questionContentDTOList, choices);
            String result = aiManager.doSyncStableRequest(AI_TEST_SCORING_SYSTEM_MESSAGE, userMessage);
            int start = result.indexOf("{");
            int end = result.lastIndexOf("}");
            String json = result.substring(start, end + 1);
            json = json.replaceAll("\\s", "");
            // 缓存结果
            answerCacheMap.put(cacheKey, json);
            // 3.构造返回值，填充答案对象的属性，
            UserAnswer userAnswer = JSONUtil.toBean(json, UserAnswer.class);
            userAnswer.setAppId(appId);
            userAnswer.setAppType(app.getAppType());
            userAnswer.setScoringStrategy(app.getScoringStrategy());
            userAnswer.setChoices(jsonStr);
            return userAnswer;
        } finally {
            if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 释放锁
                lock.unlock();
            }
        }
    }

    private String buildCacheKey(Long appId, String choices) {
        return DigestUtil.md5Hex(appId + ":" + choices);
    }

    /**
     * AI 评分用户消息封装
     * 本函数的目的是为了将用户的作答情况和应用信息一起打包成一个字符串消息
     * 这个消息将应用的名称和描述，以及用户对每个问题的选择作答情况，都包含在内
     *
     * @param app                    应用程序对象，包含应用的名称和描述
     * @param questionContentDTOList 问题内容列表，包含了用户所回答的问题
     * @param choices                用户对每个问题的选择作答
     * @return 返回打包好的用户消息字符串
     */
    private String getAiTestScoringUserMessage(App app, List<QuestionContentDTO> questionContentDTOList, List<String> choices) {
        // 初始化用户消息字符串
        StringBuilder userMessage = new StringBuilder();
        // 添加应用名称到消息中
        userMessage.append(app.getAppName()).append("\n");
        // 添加应用描述到消息中
        userMessage.append(app.getAppDesc()).append("\n");

        // 初始化问题答案列表，用于存储用户对每个问题的作答
        List<QuestionAnswerDTO> questionAnswerDTOList = new ArrayList<>();
        // 遍历问题内容列表，为每个问题创建问题答案对象，并添加到列表中
        for (int i = 0; i < questionContentDTOList.size(); i++) {
            QuestionAnswerDTO questionAnswerDTO = new QuestionAnswerDTO();
            // 设置问题标题
            questionAnswerDTO.setTitle(questionContentDTOList.get(i).getTitle());
            // 设置用户答案
            questionAnswerDTO.setUserAnswer(choices.get(i));
            // 将问题答案添加到列表中
            questionAnswerDTOList.add(questionAnswerDTO);
        }
        // 将问题答案列表转换为JSON格式字符串，并添加到用户消息中
        userMessage.append(JSONUtil.toJsonStr(questionAnswerDTOList));
        // 返回打包好的用户消息字符串
        return userMessage.toString();
    }
}

